/*
Copyright (C) 2011 The University of Michigan
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Please send inquiries to powertutor@umich.edu
*/
package vn.cybersoft.obs.andriod.batterystats2.components;
import android.util.Log;
import android.util.SparseArray;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import vn.cybersoft.obs.andriod.batterystats2.phone.PhoneConstants;
import vn.cybersoft.obs.andriod.batterystats2.service.IterationData;
import vn.cybersoft.obs.andriod.batterystats2.service.PowerData;
import vn.cybersoft.obs.andriod.batterystats2.util.Recycler;
import vn.cybersoft.obs.andriod.batterystats2.util.SystemInfo;
public class CPU extends PowerComponent {
public static class CpuData extends PowerData {
private static Recycler<CpuData> recycler = new Recycler<CpuData>();
public static CpuData obtain() {
CpuData result = recycler.obtain();
if (result != null)
return result;
return new CpuData();
}
@Override
public void recycle() {
recycler.recycle(this);
}
public double sysPerc;
public double usrPerc;
public double freq;
private CpuData() {
}
public void init(double sysPerc, double usrPerc, double freq) {
this.sysPerc = sysPerc;
this.usrPerc = usrPerc;
this.freq = freq;
}
public void writeLogDataInfo(OutputStreamWriter out) throws IOException {
StringBuilder res = new StringBuilder();
res.append("CPU-sys ").append((long) Math.round(sysPerc))
.append("\nCPU-usr ").append((long) Math.round(usrPerc))
.append("\nCPU-freq ").append(freq).append("\n");
out.write(res.toString());
}
}
private static final String TAG = "CPU";
private static final String CPU_FREQ_FILE = "/proc/cpuinfo";
private static final String STAT_FILE = "/proc/stat";
private CpuStateKeeper cpuState;
private SparseArray<CpuStateKeeper> pidStates;
private SparseArray<CpuStateKeeper> uidLinks;
private int[] pids;
private long[] statsBuf;
private PhoneConstants constants;
public CPU(PhoneConstants constants) {
this.constants = constants;
cpuState = new CpuStateKeeper(SystemInfo.AID_ALL);
pidStates = new SparseArray<CpuStateKeeper>();
uidLinks = new SparseArray<CpuStateKeeper>();
statsBuf = new long[7];
}
@Override
public IterationData calculateIteration(long iteration) {
IterationData result = IterationData.obtain();
SystemInfo sysInfo = SystemInfo.getInstance();
double freq = readCpuFreq(sysInfo);
if (freq < 0) {
Log.w(TAG, "Failed to read cpu frequency");
return result;
}
if (!sysInfo.getUsrSysTotalTime(statsBuf)) {
Log.w(TAG, "Failed to read cpu times");
return result;
}
long usrTime = statsBuf[SystemInfo.INDEX_USER_TIME];
long sysTime = statsBuf[SystemInfo.INDEX_SYS_TIME];
long totalTime = statsBuf[SystemInfo.INDEX_TOTAL_TIME];
boolean init = cpuState.isInitialized();
cpuState.updateState(usrTime, sysTime, totalTime, iteration);
if (init) {
CpuData data = CpuData.obtain();
data.init(cpuState.getUsrPerc(), cpuState.getSysPerc(), freq);
result.setPowerData(data);
}
uidLinks.clear();
pids = sysInfo.getPids(pids);
int pidInd = 0;
if (pids != null)
for (int pid : pids) {
if (pid < 0) {
break;
}
CpuStateKeeper pidState;
if (pidInd < pidStates.size() && pidStates.keyAt(pidInd) == pid) {
pidState = pidStates.valueAt(pidInd);
} else {
int uid = sysInfo.getUidForPid(pid);
if (uid >= 0) {
pidState = new CpuStateKeeper(uid);
pidStates.put(pid, pidState);
} else {
/* Assume that this process no longer exists. */
continue;
}
}
pidInd++;
if (!pidState.isStale(iteration)) {
/*
* Nothing much is going on with this pid recently. We'll
* just assume that it's not using any of the cpu for this
* iteration.
*/
pidState.updateIteration(iteration, totalTime);
} else if (sysInfo.getPidUsrSysTime(pid, statsBuf)) {
usrTime = statsBuf[SystemInfo.INDEX_USER_TIME];
sysTime = statsBuf[SystemInfo.INDEX_SYS_TIME];
init = pidState.isInitialized();
pidState.updateState(usrTime, sysTime, totalTime, iteration);
if (!init) {
continue;
}
}
CpuStateKeeper linkState = uidLinks.get(pidState.getUid());
if (linkState == null) {
uidLinks.put(pidState.getUid(), pidState);
} else {
linkState.absorb(pidState);
}
}
/* Remove processes that are no longer active. */
for (int i = 0; i < pidStates.size(); i++) {
if (!pidStates.valueAt(i).isAlive(iteration)) {
pidStates.remove(pidStates.keyAt(i--));
}
}
/* Collect the summed uid information. */
for (int i = 0; i < uidLinks.size(); i++) {
int uid = uidLinks.keyAt(i);
CpuStateKeeper linkState = uidLinks.valueAt(i);
CpuData uidData = CpuData.obtain();
predictAppUidState(uidData, linkState.getUsrPerc(),
linkState.getSysPerc(), freq);
result.addUidPowerData(uid, uidData);
}
return result;
}
/*
* This is the function that is responsible for predicting the cpu frequency
* state of the individual uid as though it were the only thing running. It
* simply is finding the lowest frequency that keeps the cpu usage under 70%
* assuming there is a linear relationship to the cpu utilization at
* different frequencies.
*/
private void predictAppUidState(CpuData uidData, double usrPerc,
double sysPerc, double freq) {
double[] freqs = constants.cpuFreqs();
if (usrPerc + sysPerc < 1e-6) {
/*
* Don't waste time with the binary search if there is no
* utilization which will be the case a lot.
*/
uidData.init(sysPerc, usrPerc, freqs[0]);
return;
}
int lo = 0;
int hi = freqs.length - 1;
double perc = sysPerc + usrPerc;
while (lo < hi) {
int mid = (lo + hi) / 2;
double nperc = perc * freq / freqs[mid];
if (nperc < 70) {
hi = mid;
} else {
lo = mid + 1;
}
}
uidData.init(sysPerc * freq / freqs[lo], usrPerc * freq / freqs[lo],
freqs[lo]);
}
private static class CpuStateKeeper {
private int uid;
private long iteration;
private long lastUpdateIteration;
private long inactiveIterations;
private long lastUsr;
private long lastSys;
private long lastTotal;
private long sumUsr;
private long sumSys;
private long deltaTotal;
private CpuStateKeeper(int uid) {
this.uid = uid;
lastUsr = lastSys = -1;
lastUpdateIteration = iteration = -1;
inactiveIterations = 0;
}
public boolean isInitialized() {
return lastUsr != -1;
}
public void updateIteration(long iteration, long totalTime) {
/*
* Process is still running but actually reading the cpu utilization
* has been skipped this iteration to avoid wasting cpu cycles as
* this process has not been very active recently.
*/
sumUsr = 0;
sumSys = 0;
deltaTotal = totalTime - lastTotal;
if (deltaTotal < 1)
deltaTotal = 1;
lastTotal = totalTime;
this.iteration = iteration;
}
public void updateState(long usrTime, long sysTime, long totalTime,
long iteration) {
sumUsr = usrTime - lastUsr;
sumSys = sysTime - lastSys;
deltaTotal = totalTime - lastTotal;
if (deltaTotal < 1)
deltaTotal = 1;
lastUsr = usrTime;
lastSys = sysTime;
lastTotal = totalTime;
lastUpdateIteration = this.iteration = iteration;
if (getUsrPerc() + getSysPerc() < 0.1) {
inactiveIterations++;
} else {
inactiveIterations = 0;
}
}
public int getUid() {
return uid;
}
public void absorb(CpuStateKeeper s) {
sumUsr += s.sumUsr;
sumSys += s.sumSys;
}
public double getUsrPerc() {
return 100.0 * sumUsr / Math.max(sumUsr + sumSys, deltaTotal);
}
public double getSysPerc() {
return 100.0 * sumSys / Math.max(sumUsr + sumSys, deltaTotal);
}
public boolean isAlive(long iteration) {
return this.iteration == iteration;
}
public boolean isStale(long iteration) {
return 1L << (iteration - lastUpdateIteration) > inactiveIterations
* inactiveIterations;
}
}
@Override
public boolean hasUidInformation() {
return true;
}
@Override
public String getComponentName() {
return "CPU";
}
/*
* Returns the frequency of the processor in Mhz. If the frequency cannot be
* determined returns a negative value instead.
*/
private double readCpuFreq(SystemInfo sysInfo) {
/*
* Try to read from the /sys/devices file first. If that doesn't work
* try manually inspecting the /proc/cpuinfo file.
*/
long cpuFreqKhz = sysInfo
.readLongFromFile("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq");
if (cpuFreqKhz != -1) {
return cpuFreqKhz / 1000.0;
}
FileReader fstream;
try {
fstream = new FileReader(CPU_FREQ_FILE);
} catch (FileNotFoundException e) {
Log.w(TAG, "Could not read cpu frequency file");
return -1;
}
BufferedReader in = new BufferedReader(fstream, 500);
String line;
try {
while ((line = in.readLine()) != null) {
if (line.startsWith("BogoMIPS")) {
return Double.parseDouble(line.trim().split("[ :]+")[1]);
}
}
} catch (IOException e) {
/* Failed to read from the cpu freq file. */
} catch (NumberFormatException e) {
/* Frequency not formatted properly as a double. */
}
Log.w(TAG, "Failed to read cpu frequency");
return -1;
}
}